﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Brjnk.Common;
using Brjnk.GpxExplorer.ViewModel;

namespace Brjnk.GpxExplorer.Computing
{
    public class VirtualTrackPoint : ITrackPoint, INotifyPropertyChanged
    {
        private TrackPointAttributeValue<DateTime> time;

        public TrackPointAttributeValue<DateTime> Time
        {
            get { return time; }
            set { time = value; OnPropertyChanged("Time"); }
        }

        private TrackPointAttributeValue<Distance> elevation;

        public TrackPointAttributeValue<Distance> Elevation
        {
            get { return elevation; }
            set { elevation = value; OnPropertyChanged("Elevation"); }
        }

        private TrackPointAttributeValue<Distance> distance;

        public TrackPointAttributeValue<Distance> Distance
        {
            get { return distance; }
            set { distance = value; OnPropertyChanged("Distance"); }
        }

        private TrackPointAttributeValue<Distance> distanceJoined;

        public TrackPointAttributeValue<Distance> DistanceJoined
        {
            get { return distanceJoined; }
            set { distanceJoined = value; OnPropertyChanged("DistanceJoined"); }
        }

        private TrackPointAttributeValue<Distance> distanceDeltaJoined;

        public TrackPointAttributeValue<Distance> DistanceDeltaJoined
        {
            get { return distanceDeltaJoined; }
            set { distanceDeltaJoined = value; OnPropertyChanged("DistanceDeltaJoined"); }
        }

        private TrackPointAttributeValue<Speed> verticalSpeedJoined;

        public TrackPointAttributeValue<Speed> VerticalSpeedJoined
        {
            get { return verticalSpeedJoined; }
            set { verticalSpeedJoined = value; OnPropertyChanged("VerticalSpeedJoined"); }
        }

        private TrackPointAttributeValue<Speed> verticalSpeed;

        public TrackPointAttributeValue<Speed> VerticalSpeed
        {
            get { return verticalSpeed; }
            set { verticalSpeed = value; OnPropertyChanged("VerticalSpeed"); }
        }

        private TrackPointAttributeValue<Speed> speedJoined;

        public TrackPointAttributeValue<Speed> SpeedJoined
        {
            get { return speedJoined; }
            set { speedJoined = value; OnPropertyChanged("SpeedJoined"); }
        }

        private TrackPointAttributeValue<Slope> slopeJoined;

        public TrackPointAttributeValue<Slope> SlopeJoined
        {
            get { return slopeJoined; }
            set { slopeJoined = value; OnPropertyChanged("SlopeJoined"); }
        }

        private TrackPointAttributeValue<TimeSpan> durationDeltaJoined;

        public TrackPointAttributeValue<TimeSpan> DurationDeltaJoined
        {
            get { return durationDeltaJoined; }
            set { durationDeltaJoined = value; OnPropertyChanged("DurationDeltaJoined"); }
        }

        private TrackPointAttributeValue<TimeSpan> duration;

        public TrackPointAttributeValue<TimeSpan> Duration
        {
            get { return duration; }
            set { duration = value; OnPropertyChanged("Duration"); }
        }

        private TrackPointAttributeValue<TimeSpan> durationJoined;

        public TrackPointAttributeValue<TimeSpan> DurationJoined
        {
            get { return durationJoined; }
            set { durationJoined = value; OnPropertyChanged("DurationJoined"); }
        }

        private TrackPointAttributeValue<Acceleration> accelerationJoined;

        public TrackPointAttributeValue<Acceleration> AccelerationJoined
        {
            get { return accelerationJoined; }
            set { accelerationJoined = value; OnPropertyChanged("AccelerationJoined"); }
        }

        private TrackPointAttributeValue<Distance> risedJoined;

        public TrackPointAttributeValue<Distance> RisedJoined
        {
            get { return risedJoined; }
            set { risedJoined = value; OnPropertyChanged("RisedJoined"); }
        }

        private TrackPointAttributeValue<Distance> rised;

        public TrackPointAttributeValue<Distance> Rised
        {
            get { return rised; }
            set { rised = value; OnPropertyChanged("Rised"); }
        }

        private TrackPointAttributeValue<Distance> fallenJoined;

        public TrackPointAttributeValue<Distance> FallenJoined
        {
            get { return fallenJoined; }
            set { fallenJoined = value; OnPropertyChanged("FallenJoined"); }
        }

        private TrackPointAttributeValue<Distance> fallen;

        public TrackPointAttributeValue<Distance> Fallen
        {
            get { return fallen; }
            set { fallen = value; OnPropertyChanged("Fallen"); }
        }

        private bool isInGap;

        public bool IsInGap
        {
            get { return isInGap; }
            set { isInGap = value; OnPropertyChanged("IsInGap"); }
        }

        public bool HasDurationDelta
        {
            set { }
        }

        public bool HasSlope
        {
            set { }
        }

        public bool HasSpeed
        {
            set { }
        }

        public bool HasAcceleration
        {
            set { }
        }

        public bool HasVerticalSpeed
        {
            set { }
        }

        public static VirtualTrackPoint GetAvgPoint(IList<ITrackPoint> points, Func<ITrackPoint, double> xValueSelector)
        {
            double xTotalDelta = xValueSelector(points.Last()) - xValueSelector(points.First());

            double distanceSum = 0, distanceJoinedSum = 0, durationSum = 0, durationJoinedSum = 0, elevationSum = 0;
            double risedSum = 0, fallenSum = 0, risedJoinedSum = 0, fallenJoinedSum = 0;

            for (int i = 0; i < points.Count - 1; i++)
            {
                var point1 = points[i];
                var point2 = points[i + 1];
                double xDelta = xValueSelector(point2) - xValueSelector(point1);

                distanceSum += (point1.Distance.Value + point2.Distance.Value) * xDelta / 2.0;
                distanceJoinedSum += (point1.DistanceJoined.Value + point2.DistanceJoined.Value) * xDelta / 2.0;
                durationSum += (point1.Duration.Value.TotalSeconds + point2.Duration.Value.TotalSeconds) * xDelta / 2.0;
                durationJoinedSum += (point1.DurationJoined.Value.TotalSeconds + point2.DurationJoined.Value.TotalSeconds) * xDelta / 2.0;
                elevationSum += (point1.Elevation.Value + point2.Elevation.Value) * xDelta / 2.0;
                risedSum += (point1.Rised.Value + point2.Rised.Value) * xDelta / 2.0;
                fallenSum += (point1.Fallen.Value + point2.Fallen.Value) * xDelta / 2.0;
                risedJoinedSum += (point1.RisedJoined.Value + point2.RisedJoined.Value) * xDelta / 2.0;
                fallenJoinedSum += (point1.FallenJoined.Value + point2.FallenJoined.Value) * xDelta / 2.0;
            }

            var retVal = new VirtualTrackPoint();
            retVal.Distance = (Distance)(distanceSum / xTotalDelta);
            retVal.DistanceJoined = (Distance)(distanceJoinedSum / xTotalDelta);
            retVal.Duration = TimeSpan.FromSeconds(durationSum / xTotalDelta);
            retVal.DurationJoined = TimeSpan.FromSeconds(durationJoinedSum / xTotalDelta);
            retVal.Elevation = (Distance)(elevationSum / xTotalDelta);
            retVal.Rised = (Distance)(risedSum / xTotalDelta);
            retVal.Fallen = (Distance)(fallenSum / xTotalDelta);
            retVal.RisedJoined = (Distance)(risedJoinedSum / xTotalDelta);
            retVal.FallenJoined = (Distance)(fallenJoinedSum / xTotalDelta);
            return retVal;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        public static VirtualTrackPoint CreateFirst()
        {
            var retVal = new VirtualTrackPoint();
            retVal.Distance = (Distance)0;
            retVal.DistanceJoined = (Distance)0;
            retVal.Duration = TimeSpan.Zero;
            retVal.DurationJoined = TimeSpan.Zero;
            retVal.Elevation = TrackPointAttributeValue<Distance>.CreateUnavailable();
            retVal.Rised = TrackPointAttributeValue<Distance>.CreateUnavailable();
            retVal.Fallen = TrackPointAttributeValue<Distance>.CreateUnavailable();
            retVal.RisedJoined = TrackPointAttributeValue<Distance>.CreateUnavailable();
            retVal.FallenJoined = TrackPointAttributeValue<Distance>.CreateUnavailable();
            return retVal;
        }
    }
}